﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using HB.Expand.CRC;
using System.Runtime.InteropServices;

namespace HB.Expand
{
    public static class ByteList2Object
    {
        /// <summary>
        /// 计算数组的md5值
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        public static string ToMD5(this byte[] list)
        {
            using (MD5 md5 = new MD5CryptoServiceProvider())
            {
                return String2Hash.ToHash(md5, new MemoryStream(list));
            }
        }
        internal static string ToStringByHashBytes(this byte[] hash)
        {
            string result = BitConverter.ToString(hash);
            result = result.Replace("-", string.Empty).ToLower();
            return result;
        }
        /// <summary>
        /// 计算CRC8 MAXIM
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        public static byte ToCRC8MAXIM(this byte[] list)
        {
            return CRC8MAXIM.CRC(list);
        }
        /// <summary>
        /// 计算CRC-16/MODBUS
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        public static ushort ToCRC16MODBUS(this byte[] list)
        {
            return CRC16MODBUS.CRC(list);
        }
        /// <summary>
        /// 计算CRC8 MAXIM
        /// </summary>
        /// <param name="list"></param>
        /// <param name="offset"></param>
        /// <param name="len"></param>
        /// <returns></returns>
        public static byte ToCRC8MAXIM(this byte[] list, int offset, int len)
        {
            return CRC8MAXIM.CRC(list, offset, len);
        }
        /// <summary>
        /// 计算CRC-16/MODBUS
        /// </summary>
        /// <param name="list"></param>
        /// <param name="offset"></param>
        /// <param name="len"></param>
        /// <returns></returns>
        public static ushort ToCRC16MODBUS(this byte[] list, int offset, int len)
        {
            return CRC16MODBUS.CRC(list, offset, len);
        }
        /// <summary>
        /// 按照指定的编码解析数组为字符串
        /// </summary>
        /// <param name="data"></param>
        /// <param name="encoding">default=utf-8</param>
        /// <returns></returns>
        public static string ToEncodingString(this byte[] data, Encoding encoding = null)
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }
            return encoding.GetString(data);
        }
        /// <summary>
        /// 将数组转为易读的16进制字符串
        /// </summary>
        /// <param name="data"></param>
        /// <param name="splitString"></param>
        /// <param name="smallCase"></param>
        /// <returns></returns>
        public static string ToHexStringWithSplit(this byte[] data, string splitString = " ", bool smallCase = true)
        {
            string Result = null;
            string hexcase = smallCase ? "x2" : "X2";
            if (data != null)
            {
                StringBuilder sb = new StringBuilder();
                foreach (byte bb in data)
                {
                    sb.Append(bb.ToString(hexcase));
                    sb.Append(splitString);
                }
                Result = sb.ToString();
            }
            return Result;
        }
        /// <summary>
        /// 将字节流中的数据按长度分组，然后依次反转内部指定长度的小组顺序。可用于大小端换序。
        /// </summary>
        /// <param name="data">数据总长度</param>
        /// <param name="startIndex">操作开始索引</param>
        /// <param name="len">操作长度</param>
        /// <param name="groupLen">数据组长度</param>
        /// <param name="singleLen">组内单元长度</param>
        /// <returns></returns>
        public static byte[] TurnAround(this byte[] data, int startIndex, int len, int groupLen, int singleLen)
        {
            if (startIndex + len > data.Length) { throw new Exception("len Error"); }
            if (len % groupLen != 0) { throw new Exception("Group Error"); }
            if (groupLen % singleLen != 0) { throw new Exception("single Error"); }
            if (groupLen < singleLen) { throw new Exception("single Error"); }
            List<byte> result = new List<byte>(data.Length);
            for (int i = 0; i < startIndex; i++)
            {
                result.Add(data[i]);
            }
            for (int i = startIndex; i < startIndex + len; i += groupLen)
            {
                for (int j = 1; j <= groupLen / singleLen; j++)
                {
                    int Singleptr = i + groupLen - singleLen * j;
                    for (int z = 0; z < singleLen; z++)
                    {
                        int ptr = Singleptr + z;
                        result.Add(data[ptr]);
                    }
                }
            }
            for (int i = startIndex + len; i < data.Length; i++)
            {
                result.Add(data[i]);
            }
            return result.ToArray();
        }
        /// <summary>
        /// 对象匹配结果
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class MapResult<T>
        {
            /// <summary>
            /// 是否成功
            /// </summary>
            public bool IsOk { get; set; }
            /// <summary>
            /// 实例对象
            /// </summary>
            public T Data { get; set; }
            /// <summary>
            /// 错误信息
            /// </summary>
            public string ErrorMsg { get; set; }
        }
        class Tempdata
        {
            public int Ptr { get; set; }
            public object Data { get; set; }
            public Tempdata(object data, int ptr)
            {
                this.Ptr = ptr;
                this.Data = data;
            }
        }
        /// <summary>
        /// stream to class,the support type {byte,sbyte,bool,short ,ushort,uint,int,long,ulong,float,double}and the List of them,the List Must Have the ArrayLength Attribute
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        /// <param name="isLittleEndian"></param>
        /// <returns></returns>
        public static MapResult<T> MapToClass<T>(this byte[] data, bool isLittleEndian = true) where T : class, new()
        {
            var instance = new T();
            return MapToClass(data, instance, isLittleEndian);
        }
        /// <summary>
        /// stream to class,the support type {byte,sbyte,bool,short ,ushort,uint,int,long,ulong,float,double}and the List of them,the List Must Have the ArrayLength Attribute
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        /// <param name="modelInstance">提供模型和存储介质</param>
        /// <param name="isLittleEndian"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static MapResult<T> MapToClass<T>(this byte[] data, T modelInstance, bool isLittleEndian = true) where T : class
        {
            MapResult<T> result = new MapResult<T>();
            if (data == null) { result.ErrorMsg = "stream can't be null"; return result; }
            if (data.Length == 0) { result.ErrorMsg = "stream is Empty"; return result; }
            if (modelInstance == null) { result.ErrorMsg = "modelInstance is Empty"; return result; }
            result.Data = modelInstance;
            bool isPaltFormLettleEnding = 0.IsLettleEndingPlatfrom();
            bool isNeedReserve = isPaltFormLettleEnding != isLittleEndian;
            Dictionary<Type, Func<byte[], int, PropertyInfo, Tempdata>> dicmap = new Dictionary<Type, Func<byte[], int, PropertyInfo, Tempdata>>()
            {
                { typeof (sbyte), (bytes,pt,p)=>{object o=null;if(bytes!=null ){ o=bytes[pt]>127?(sbyte )(bytes [pt]-256):(sbyte )bytes[pt]; } return new Tempdata (o,1+pt) ;  } },
                { typeof (byte),  (bytes,pt,p)=>{object o=null;if(bytes!=null ){ o=bytes[pt ]; } return new Tempdata (o , 1+pt );  } },
                { typeof (bool),  (bytes,pt,p)=>{object o=null;if(bytes!=null ){ o=bytes[pt ]!=0; } return new Tempdata (o, 1+pt) ;  } },
                { typeof (ushort),(bytes,pt,p)=>{object o=null;if(bytes!=null ){ o = BitConverter.ToUInt16(bytes ,pt ).ByteReserse (!isNeedReserve ); } return new Tempdata (o, 2+pt );  } },
                { typeof (short), (bytes,pt,p)=>{object o=null;if(bytes!=null ){ o = BitConverter.ToInt16(bytes ,pt ).ByteReserse (!isNeedReserve ); } return new Tempdata (o, 2+pt ) ;  } },
                { typeof (int),   (bytes,pt,p)=>{object o=null;if(bytes!=null ){ o = BitConverter.ToInt32(bytes ,pt ).ByteReserse (!isNeedReserve ); } return new Tempdata (o, 4+pt  );  } },
                { typeof (uint),  (bytes,pt,p)=>{object o=null;if(bytes!=null ){ o = BitConverter.ToUInt32(bytes ,pt ).ByteReserse (!isNeedReserve ); } return new Tempdata (o, 4+pt ) ;  } },
                { typeof (float), (bytes,pt,p)=>{object o=null;if(bytes!=null ){ o = BitConverter.ToSingle(bytes ,pt ).ByteReserse (!isNeedReserve ); } return new Tempdata (o, 4+pt ) ;  } },
                { typeof (long),  (bytes,pt,p)=>{object o=null;if(bytes!=null ){ o = BitConverter.ToInt64(bytes ,pt ).ByteReserse (!isNeedReserve ); } return new Tempdata (o, 8+pt ) ;  } },
                { typeof (ulong), (bytes,pt,p)=>{object o=null;if(bytes!=null ){ o = BitConverter.ToUInt64(bytes ,pt ).ByteReserse (!isNeedReserve ); } return new Tempdata (o, 8+pt  );  } },
                { typeof (double),(bytes,pt,p)=>{object o=null;if(bytes!=null ){ o = BitConverter.ToDouble(bytes ,pt ).ByteReserse (!isNeedReserve ); } return new Tempdata (o, 8+pt ) ;  } },
 };
            //array

            dicmap.Add(typeof(sbyte[]), (bytes, pt, p) => { return DoForList<sbyte>(bytes, pt, p); });
            dicmap.Add(typeof(byte[]), (bytes, pt, p) => { return DoForList<byte>(bytes, pt, p); });
            dicmap.Add(typeof(bool[]), (bytes, pt, p) => { return DoForList<bool>(bytes, pt, p); });
            dicmap.Add(typeof(ushort[]), (bytes, pt, p) => { return DoForList<ushort>(bytes, pt, p); });
            dicmap.Add(typeof(short[]), (bytes, pt, p) => { return DoForList<short>(bytes, pt, p); });
            dicmap.Add(typeof(int[]), (bytes, pt, p) => { return DoForList<int>(bytes, pt, p); });
            dicmap.Add(typeof(uint[]), (bytes, pt, p) => { return DoForList<uint>(bytes, pt, p); });
            dicmap.Add(typeof(float[]), (bytes, pt, p) => { return DoForList<float>(bytes, pt, p); });
            dicmap.Add(typeof(long[]), (bytes, pt, p) => { return DoForList<long>(bytes, pt, p); });
            dicmap.Add(typeof(ulong[]), (bytes, pt, p) => { return DoForList<ulong>(bytes, pt, p); });
            dicmap.Add(typeof(double[]), (bytes, pt, p) => { return DoForList<double>(bytes, pt, p); });
            Tempdata DoForList<Titem>(byte[] bytes, int startindex, PropertyInfo p)
            {
                var type = typeof(Titem);
                var array = p.GetValue(modelInstance, null) as Array;
                if (array == null)
                {
                    throw new Exception($"{p.Name}Should Have Init");
                }
                int len = array.Length;
                int endptr = startindex + len * dicmap[type](null, 0, p).Ptr;
                if (bytes != null)
                {
                    for (int i = 0; i < len; i++)
                    {
                        var temp = dicmap[type](bytes, startindex, p);
                        startindex = temp.Ptr;
                        array.SetValue(temp.Data, i);
                    }
                }
                return new Tempdata(array, endptr);
            }
            var t = modelInstance.GetType();
            int properityMapLen = 0;
            var properites = t.GetProperties();
            foreach (var p in properites)
            {
                if (dicmap.ContainsKey(p.PropertyType))
                {
                    properityMapLen += dicmap[p.PropertyType](data, 0, p).Ptr;
                }
                else
                {
                    result.ErrorMsg = $"{p.PropertyType.ToString()} is not support"; return result;
                }
            }
            if (data.Length != properityMapLen)
            {
                result.ErrorMsg = $"Model request length={properityMapLen},but input length={data.Length}";
                return result; ;
            }
            int ptr = 0;
            try
            {
                foreach (var p in properites)
                {
                    var temp = dicmap[p.PropertyType](data, ptr, p);
                    ptr = temp.Ptr;
                    p.SetValue(result.Data, temp.Data, null);
                }
                result.IsOk = true;
            }
            catch (Exception ex)
            {
                result.ErrorMsg = ex.Message;
            }
            return result;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        /// <param name="obj"></param>
        /// <param name="expectDataLen">改结构期望的输入长度，小于0则不检查</param>
        /// <returns></returns>
        public static bool MapToStruct<T>(this byte[] data, out T obj, int expectDataLen = -1) where T : struct
        {
            obj = default(T);
            if (expectDataLen > 0 && data.Length != expectDataLen) { return false; }
            var type = typeof(T);
            int size = Marshal.SizeOf(type);
            IntPtr structPtr = Marshal.AllocHGlobal(size);
            var len = Math.Min(data.Length, size);
            Marshal.Copy(data, 0, structPtr, len);
            obj = (T)Marshal.PtrToStructure(structPtr, type);
            Marshal.FreeHGlobal(structPtr);
            return true;
        }
    }
}
